package plugin.demo.shardingsphere.xa.config.v5_0_0.algorithms;

import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.complex.ComplexKeysShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.hint.HintShardingAlgorithm;
import org.apache.shardingsphere.sharding.api.sharding.hint.HintShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/4/23 上午10:53
 * @UpdateDate:
 * @Description:
 */
public class ELParseAlgorithm {

    /**
     * 基于El-expression的基础路由解析，针对单列路由。
     */
    public static class StandardElParseAlgorithm implements StandardShardingAlgorithm<Comparable<Object>>{
        private static final String ROUTING_EL_PARSER =  "el-expression";
        //函数具备单调性. ++ 单调递增，-- 单调递减
        private static final String ROUTING_MONOTONICITY =  "monotonicity";

        @Getter
        @Setter
        private Properties props = new Properties();

        private String elExpress;
        private Expression expression;
        /*el算法是否具备单调性 monotonicity:? */
        private boolean isMonotonicity = false;
        /*单调递增 monotonicity: ++ */
        private boolean monotonicityUp = false;
        /*单调递减 monotonicity: -- */
        private boolean monotonicityDn = false;

        /**
         *
         * @param availableTargetNames 暂时库表.
         * @param shardingValue 简单条件序列.
         * @return 返回该条数据路由的真实库表.
         * doc: El表达式在扩大路由支持粒度的同时，由于函数的结果无法逆推导致在range条件下无法反向递推 DQL/DML 分区列所落在的那些目标实例，
         * 但同时引入了
         *
         */
        @Override
        public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Comparable<Object>> shardingValue) {
            StandardEvaluationContext context = new StandardEvaluationContext();
            context.setVariable("$table",shardingValue.getLogicTableName());
            //col_name -> col_val(Comparable<Object>).
            context.setVariable(shardingValue.getColumnName(),shardingValue.getValue());
            Object suffix = expression.getValue(context,Object.class);

            for (Object targetName : availableTargetNames) {
                String targetItem = String.valueOf(targetName);
                if(targetItem.endsWith(String.valueOf(suffix))){
                    return targetItem;
                }
            }
            throw new RuntimeException("No target for routing ["+shardingValue.getLogicTableName() + "."+ shardingValue.getColumnName() + " = " +shardingValue.getValue()+ "]");
        }

        @Override
        public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Comparable<Object>> shardingValue) {
            if(!isMonotonicity){
                return availableTargetNames;
            }
            //上行的range left/right.
            Range range = shardingValue.getValueRange();
            Long rangeLeft = range.hasLowerBound() ? Long.parseLong(range.lowerEndpoint().toString()) : 0L;
            Long rangeRight = range.hasUpperBound() ? Integer.parseInt(range.upperEndpoint().toString()) : Long.MAX_VALUE;

            //运算range left/right 路由的极值.
            StandardEvaluationContext context = new StandardEvaluationContext();
            context.setVariable("$table",shardingValue.getLogicTableName());
            context.setVariable(shardingValue.getColumnName(),rangeLeft);
            Object rangeLeftTail = expression.getValue(context, rangeLeft.toString(), Object.class);
            context.setVariable(shardingValue.getColumnName(),rangeRight);
            Object rangeRightTail = expression.getValue(context, rangeRight.toString(), Object.class);

            //将极值转为Long, 根据函数单调性返回目标路由对象.
            Long left = Long.valueOf(rangeLeftTail.toString());
            Long right = Long.valueOf(rangeRightTail.toString());
            Pattern pattern = Pattern.compile("\\d*$");

            if(this.monotonicityUp){
                Set<String> collect = availableTargetNames.stream()
                        .filter(item -> {
                            Matcher matcher = pattern.matcher(item);
                            matcher.find();
                            String suffix = matcher.group(0);
                            if(suffix != null){
                                Long targetSuf = Long.valueOf(suffix);
                                if(targetSuf <= right && targetSuf >= left){
                                    return true;
                                }
                                return false;
                            }
                            return false;
                        })
                        .collect(Collectors.toSet());
                return collect;
            }else if(this.monotonicityDn){
                Set<String> collect = availableTargetNames.stream()
                        .filter(item -> {
                            Matcher matcher = pattern.matcher(item);
                            matcher.find();
                            String suffix = matcher.group(0);
                            if(suffix != null){
                                Long targetSuf = Long.valueOf(suffix);
                                if(targetSuf <= left && targetSuf >= right){
                                    return true;
                                }
                                return false;
                            }
                            return false;
                        })
                        .collect(Collectors.toSet());
                return collect;
            }else {
                return availableTargetNames;
            }
        }


        @Override
        public void init() {
            //解析El-expression.
            this.elExpress = this.props.getProperty(ROUTING_EL_PARSER,null);
            Preconditions.checkNotNull(this.elExpress, "ElParser sharding el-expression cannot be null.");
            this.expression = new SpelExpressionParser().parseExpression(this.elExpress);

            //获取函数单调性配置.
            String monotonicity = this.props.getProperty(ROUTING_MONOTONICITY, null);
            if(monotonicity != null && (monotonicity = monotonicity.trim()) != null){
                this.isMonotonicity = true;
                if(monotonicity.equals("++")){
                    this.monotonicityUp = true;
                }else if(monotonicity.equals("--")){
                    this.monotonicityDn = true;
                }else {
                    this.isMonotonicity = false;
                    throw new IllegalStateException("ElParser sharding monotonicity format error [++, --].");
                }
            }
        }
        @Override
        public String getType() {
            return "STANDARD_EL_PARSER";
        }
    }


    /**
     * 复合路由解析.
     */
    public static class ComplexElParseAlgorithm implements ComplexKeysShardingAlgorithm<Comparable<Object>> {
        private static final String ROUTING_EL_PARSER =  "el-expression";
        //函数具备单调性. ++ 单调递增，-- 单调递减
        private static final String ROUTING_MONOTONICITY =  "monotonicity";

        @Getter
        @Setter
        private Properties props = new Properties();

        private String elExpress;
        private Expression expression;
        private boolean isMonotonicity = false;
        private boolean monotonicityUp = false;
        private boolean monotonicityDn = false;

        @Override
        public void init() {
            //解析El-expression.
            this.elExpress = this.props.getProperty(ROUTING_EL_PARSER,null);
            Preconditions.checkNotNull(this.elExpress, "ElParser sharding el-expression cannot be null.");
            this.expression = new SpelExpressionParser().parseExpression(this.elExpress);

            //获取函数单调性配置.
            String monotonicity = this.props.getProperty(ROUTING_MONOTONICITY, null);
            if(monotonicity != null && (monotonicity = monotonicity.trim()) != null){
                this.isMonotonicity = true;
                if(monotonicity.equals("++")){
                    this.monotonicityUp = true;
                }else if(monotonicity.equals("--")){
                    this.monotonicityDn = true;
                }else {
                    this.isMonotonicity = false;
                    throw new IllegalStateException("ElParser sharding monotonicity format error [++, --].");
                }
            }
        }
        @Override
        public String getType() {
            return "COMPLEX_EL_PARSER";
        }

        @Override
        public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<Comparable<Object>> shardingValue) {
            Map<String, Collection<Comparable<Object>>> columnNameAndShardingValuesMap = shardingValue.getColumnNameAndShardingValuesMap();
            Map<String, Range<Comparable<Object>>> columnNameAndRangeValuesMap = shardingValue.getColumnNameAndRangeValuesMap();
            String logicTableName = shardingValue.getLogicTableName();
            /*这里编程复杂度极高，也是日后sharding-JDBC完善的主要工作量，为apache ShardingSphere致敬...*/
            return null;
        }
    }


    /**
     * Hint路由解析，由HintManager驱动路由.
     */
    @Getter
    @Setter
    public static class HintElParserAlgorithm implements HintShardingAlgorithm<Comparable<Object>>{

        @Getter
        @Setter
        private Properties props = new Properties();

        @Override
        public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Comparable<Object>> shardingValue) {
            Collection<Comparable<Object>> values = shardingValue.getValues();
            Comparable<Object> next = shardingValue.getValues().iterator().next();
            String suffix = String.valueOf(next);
            Set<String> collect = availableTargetNames.stream().filter(item -> item.endsWith(suffix)).collect(Collectors.toSet());
            return collect;
        }

        @Override
        public void init() {
            //Do Nothing.
        }

        @Override
        public String getType() {
            return "HINT_EL_PARSER";
        }
    }

}
